﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Runtime.InteropServices;
using System.Windows.Interop;

namespace Nox.Amuse
{
    /// <summary>
    /// Provides extension methods for Windows
    /// </summary>
    public static class WindowExtensionMethods
    {
        /// <summary>
        /// Moves a window if its current bounds are not within the visible WorkArea
        /// </summary>
        /// <param name="window">The window being corrected</param>
        public static void CorrectWindowPosition(this Window window)
        {
            if (window.RestoreBounds.Right + 1 >= SystemParameters.WorkArea.Right)
                window.Left = SystemParameters.WorkArea.Right - window.ActualWidth - SystemParameters.BorderWidth * 4;
            if (window.RestoreBounds.Bottom + 1 >= SystemParameters.WorkArea.Bottom)
                window.Top = SystemParameters.WorkArea.Bottom - window.ActualHeight - SystemParameters.CaptionHeight - SystemParameters.BorderWidth * 4;
            if (window.RestoreBounds.Left <= SystemParameters.WorkArea.Left)
                window.Left = SystemParameters.WorkArea.Left + SystemParameters.BorderWidth * 4;
            if (window.RestoreBounds.Top <= SystemParameters.WorkArea.Top)
                window.Top = SystemParameters.WorkArea.Top + SystemParameters.CaptionHeight + SystemParameters.BorderWidth * 4;
        }

        #region InterOp Code for HideMinimizeAndMaximizeButtons

        /// <summary>
        /// Changes an attribute of the specified window. Used on 32-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be set.</param>
        /// <param name="dwNewLong">Specifies the replacement value.</param>
        /// <returns>If the function succeeds, the return value is the previous value of the specified offset.
        /// If the function fails, the return value is zero.
        /// </returns>
        [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
        private static extern int SetWindowLong32(IntPtr hWnd, int nIndex, int dwNewLong);
        /// <summary>
        /// Changes an attribute of the specified window. Used on 64-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be set.</param>
        /// <param name="dwNewLong">Specifies the replacement value.</param>
        /// <returns>If the function succeeds, the return value is the previous value of the specified offset.
        /// If the function fails, the return value is zero.
        /// </returns>
        [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
        private static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
        /// <summary>
        /// Changes an attribute of the specified window
        /// Calls the correct function for 32-bit or 64-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be set.</param>
        /// <param name="dwNewLong">Specifies the replacement value.</param>
        /// <returns>If the function succeeds, the return value is the previous value of the specified offset.
        /// If the function fails, the return value is zero.
        /// </returns>
        public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
        {
            if (IntPtr.Size == 8)
                return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
            else
                return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
        }

        /// <summary>
        /// Retrieves information about the specified window. Used on 32-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be retrieved</param>
        /// <returns>If the function succeeds, the return value is the requested value.
        /// If the function fails, the return value is zero.
        /// </returns>
        [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
        private static extern IntPtr GetWindowLongPtr32(IntPtr hWnd, int nIndex);
        /// <summary>
        /// Retrieves information about the specified window. Used on 64-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be retrieved</param>
        /// <returns>If the function succeeds, the return value is the requested value.
        /// If the function fails, the return value is zero.
        /// </returns>
        [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
        private static extern IntPtr GetWindowLongPtr64(IntPtr hWnd, int nIndex);
        /// <summary>
        /// Retrieves information about the specified window.
        /// Calls the correct function for 32-bit or 64-bit Windows.
        /// </summary>
        /// <param name="hWnd">Handle for the Window</param>
        /// <param name="nIndex">Specifies the zero-based offset to the value to be retrieved</param>
        /// <returns>If the function succeeds, the return value is the requested value.
        /// If the function fails, the return value is zero.
        /// </returns>
        internal static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
        {
            if (IntPtr.Size == 8)
                return GetWindowLongPtr64(hWnd, nIndex);
            else
                return GetWindowLongPtr32(hWnd, nIndex);
        }

        /// <summary>
        /// Removes the Minimize and Maximize buttons from a window and disables the options in the context menu
        /// </summary>
        /// <remarks>This method should be called as follows:
        /// this.SourceInitialized += (x, y) => { this.HideMinimizeAndMaximizeButtons(); };</remarks>
        /// <param name="window">The Window to remove the buttons from</param>
        internal static void HideMinimizeAndMaximizeButtons(this Window window)
        {
            // Code sample from: http://stackoverflow.com/questions/339620/how-do-i-remove-minimize-and-maximize-from-a-resizable-window-in-wpf
            const int GWL_STYLE = -16; //Retrieves the window styles
            IntPtr hwnd = new WindowInteropHelper(window).Handle;
            long value = GetWindowLongPtr(hwnd, GWL_STYLE).ToInt64();
            if (value == 0)
                throw new InvalidOperationException("Error while trying to find window styles. Please report this as a bug.");
            SetWindowLongPtr(hwnd, GWL_STYLE, new IntPtr(value & -131073 & -65537));
        }

        #endregion

        #region InterOp Code for FlashWindow

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool FlashWindowEx(ref FLASHWINFO pwfi);

        [StructLayout(LayoutKind.Sequential)]
        public struct FLASHWINFO
        {
            public UInt32 cbSize;
            public IntPtr hwnd;
            public UInt32 dwFlags;
            public UInt32 uCount;
            public UInt32 dwTimeout;
        }

        //Stop flashing. The system restores the window to its original state. 
        public const UInt32 FLASHW_STOP = 0;
        //Flash the window caption. 
        public const UInt32 FLASHW_CAPTION = 1;
        //Flash the taskbar button. 
        public const UInt32 FLASHW_TRAY = 2;
        //Flash both the window caption and taskbar button.
        //This is equivalent to setting the FLASHW_CAPTION | FLASHW_TRAY flags. 
        public const UInt32 FLASHW_ALL = 3;
        //Flash continuously, until the FLASHW_STOP flag is set. 
        public const UInt32 FLASHW_TIMER = 4;
        //Flash continuously until the window comes to the foreground. 
        public const UInt32 FLASHW_TIMERNOFG = 12;

        /// <summary>
        /// Flashes a window until the window comes to the foreground
        /// </summary>
        /// <param name="window">The window to flash</param>
        /// <param name="state">The state for the window to enter</param>
        /// <returns>Whether or not the window needed flashing</returns>
        private static bool FlashWindowEx(Window window, uint state)
        {
            IntPtr hwnd = new WindowInteropHelper(window).Handle;
            FLASHWINFO fInfo = new FLASHWINFO();

            fInfo.cbSize = Convert.ToUInt32(Marshal.SizeOf(fInfo));
            fInfo.hwnd = hwnd;
            fInfo.dwFlags = state;
            fInfo.uCount = (uint)(Environment.OSVersion.Version.Major >= 6 && Environment.OSVersion.Version.Minor > 0 ? 1 : 2);
            fInfo.dwTimeout = 0;

            return FlashWindowEx(ref fInfo);
        }

        public static void StartTaskBarFlash(this Window window)
        {
            FlashWindowEx(window, FLASHW_TRAY);
        }

        public static void StopTaskBarFlash(this Window window)
        {
            FlashWindowEx(window, FLASHW_STOP);
        }

        #endregion

    }
}
